package com.btb.adapter.service;

import java.math.BigInteger;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import org.web3j.protocol.Web3j;
import org.web3j.protocol.core.methods.response.TransactionReceipt;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.btb.adapter.constant.Constants;
import com.btb.adapter.constant.EventType;
import com.btb.adapter.exception.GameException;
import com.btb.adapter.mapper.OffChainMapper;
import com.btb.adapter.mapper.UpChainFishMapper;
import com.btb.adapter.mapper.UpChainHeroMapper;
import com.btb.adapter.model.OffChain;
import com.btb.adapter.model.TaskConfig;
import com.btb.adapter.model.TransferRecord;
import com.btb.adapter.model.UpChainFish;
import com.btb.adapter.model.UpChainHero;
import com.btb.adapter.utils.Web3jUtils;
import com.btb.adapter.vo.request.OffChainEventCallBack;
import com.btb.adapter.vo.request.PayRequest;
import com.btb.adapter.vo.request.UpChainEventCallBack;
import com.btb.adapter.vo.response.CommonResponse;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;

import lombok.extern.slf4j.Slf4j;

/**
 * 远程通知确认
 *
 * @author hqbzl
 *
 */

@Slf4j
@Service
public class CallBackService {

	@Value("${adapter.upChainHeroCallBack.url}")
	private String upChainHeroCallBackUrl;

	@Value("${adapter.upChainFishCallBack.url}")
	private String upChainFishCallBackUrl;

	@Value("${adapter.offChainCallBack.url}")
	private String offChainCallBackUrl;

	@Value("${adapter.request.sign}")
	private String sign;

	@Autowired
	private RestTemplate restTemplate;
	@Autowired
	private TransferRecordService transferRecordService;
	@Autowired
	private WatchTransferService watchTransferService;
	@Autowired
	private Web3j web3j;
	@Autowired
	private TaskConfigService taskConfigService;
	@Autowired
	private UpChainHeroMapper upChainHeroMapper;
	@Autowired
	private UpChainFishMapper upChainFishMapper;
	@Autowired
	private OffChainMapper offChainMapper;

	/**
	 *
	 * 判断事件收到的区块和当前的区块的偏移量是不是比设置的大
	 *
	 * 如果区块检查有问题,以后都不会在进行推送
	 *
	 *
	 * @param transferRecord
	 * @param taskConfig
	 */
	public TaskConfig start(EventType type, String taskName) {
		final TaskConfig taskConfig = taskConfigService.findConfigByTaskNameCache(taskName);
		if (Objects.isNull(taskConfig)) {
			log.error("{} taskConfig is null", taskName);
			return null;
		}
		Long status = Optional.ofNullable(taskConfig.getStatus()).orElse(1L);
		if (Objects.equals(1L, status)) {
			log.error("{} taskConfig status is 1 stop", taskName);
			return null;
		}
		return taskConfig;
	}

	public boolean check(TaskConfig taskConfig, final Long blockNumber,final BigInteger maxBlockNumber, String transactionHash) {
		Long status = Optional.ofNullable(taskConfig.getStatus()).orElse(1L);
		if (Objects.equals(1L, status)) {
			log.error("{} taskConfig status is 1 stop", taskConfig.getTaskName());
			return false;
		}
		if (Objects.isNull(maxBlockNumber)) {
			return false;
		}
		// 设置的偏移量
		final Long offset = Optional.ofNullable(taskConfig.getOffset()).orElse(0L);
		// 收到支付事件时的区块高度
		// 实际的差值
		long actOffset = maxBlockNumber.longValue() - blockNumber.longValue();
		if (actOffset < offset) {
//			String notifyCheckResult = String.format("config offset:%s, actOffset:%s check is false", offset,
//					actOffset);
			log.error("config offset:{}, actOffset:{} check is false", offset, actOffset);
			return false;
		}
		// 防止链上撤消, 检查失败的, 再也不会去尝试推送了
		// 此处也是有可能有异常的, 交给框架了
		if (!againCheckTransfer(transactionHash)) {
			log.error("againCheckTransfer is rollback transactionHash:{}", transactionHash);
			return false;
		}
		return true;
//
//		String notifyStatus = Constants.SUCCESS;  //请求的结果,是不是可以正常访问
//		String response = StringUtils.EMPTY;
//		String notifyResult = StringUtils.EMPTY;  //  推送的结果,包含响应的解析, 是否可以解析出成功的响应
//		final PayRequest payRequest = buildPayRequest(transferRecord);
//		try {
//			String body = confirmPayHttpRequet(payRequest);
//			if(checkResponse(body)) {
//				notifyResult = Constants.SUCCESS;
//			}
//		} catch (Exception e) {
//			notifyStatus = Constants.FAIL;
//			response = e.getMessage();
//		}
//		transferRecordService.updateConfirmPay(transferRecord.getId(), payRequest,maxBlockNumber.longValue(), notifyStatus, response, notifyResult);
	}

	private boolean checkResponse(String body) {
		CommonResponse<String> parseObject = JSON.parseObject(body, new TypeReference<CommonResponse<String>>() {
		});
		return parseObject.isOk();
	}

	private boolean againCheckTransfer(final String transactionHash) {
		try {
			TransactionReceipt result = Web3jUtils.query(web3j, transactionHash);
			if (Objects.isNull(result)) {
				return false;
			}
			return result.isStatusOK();
		} catch (Exception e) {
			log.error("againCheckTransfer err: {}", e.getMessage(), e);
			throw new GameException(e.getMessage());
		}
	}

	private String post(final String url, final String jsonContent) {
		HttpHeaders headers = new HttpHeaders();
		headers.add("sign", sign);
		headers.setContentType(MediaType.APPLICATION_JSON);
		HttpEntity<String> request = new HttpEntity<>(jsonContent, headers);
		try {
			ResponseEntity<String> postForEntity = restTemplate.postForEntity(url, request, String.class);
			return postForEntity.getBody();
		} catch (Exception e) {
			log.error("confirmPayHttpRequet err: {}", e.getMessage(), e);
			throw new GameException(e.getMessage());
		}
	}

	private PayRequest buildPayRequest(final TransferRecord transferRecord) {
		return PayRequest.builder().requestId(UUID.randomUUID().toString())
				.transactionHash(transferRecord.getTransactionHash()).address(transferRecord.getFromAddress())
				.status(Boolean.parseBoolean(transferRecord.getStatus())).orderId(transferRecord.getPayOrderId())
				.type(transferRecord.getPayType())
				.orderPrice(transferRecord.getPayOrderPrice().stripTrailingZeros().toPlainString())
				.time(Long.parseLong(transferRecord.getTimestamp())).build();
	}

	public void upFish() {
		TaskConfig taskConfig = start(EventType.FISH_CHAIN_UP, Constants.UP_CHAIN_FISH_CALL_BACK);
		if (Objects.isNull(taskConfig)) {
			return;
		}
		final List<UpChainFish> list = queryFish(taskConfig);
		for (UpChainFish fish : list) {
			final BigInteger maxBlockNumber = watchTransferService.getBlockNumber();
			boolean check = check(taskConfig, fish.getBlockNumber(), maxBlockNumber, fish.getTransactionHash());
			if (!check) {
				continue;
			}
			final UpChainEventCallBack callBack = UpChainEventCallBack.builder()
					.requestId(UUID.randomUUID().toString())
					.transactionHash(fish.getTransactionHash())
					.type(EventType.HERO_CHAIN_UP.getType())
					.status(check)
					.clientId(fish.getClientId())
					.build();
			final String jsonContent = JSON.toJSONString(callBack);
			// 推送响就成功
			String notifyStatus = StringUtils.EMPTY;
			String notifyResult = StringUtils.EMPTY;
			String body = StringUtils.EMPTY;
			try {
				body = post(upChainFishCallBackUrl, jsonContent);
				if (checkResponse(body)) {
					notifyStatus = "success";
				}
			} catch (Exception e) {
				log.error("upHero err:{}", e.getMessage(), e);
				notifyResult = e.getMessage();
			}
			updateChainFishCallBack(fish.getId(), callBack, maxBlockNumber.longValue(), notifyStatus, body, notifyResult);
		}
	}

	public void upHero() {
		final TaskConfig taskConfig = start(EventType.HERO_CHAIN_UP, Constants.UP_CHAIN_HERO_CALL_BACK);
		if (Objects.isNull(taskConfig)) {
			return;
		}
		final List<UpChainHero> list = queryHero(taskConfig);
		for (UpChainHero hero : list) {
			final BigInteger maxBlockNumber = watchTransferService.getBlockNumber();
			boolean check = check(taskConfig, hero.getBlockNumber(), maxBlockNumber, hero.getTransactionHash());
			if (!check) {
				continue;
			}
			final UpChainEventCallBack callBack = UpChainEventCallBack.builder()
					.requestId(UUID.randomUUID().toString())
					.transactionHash(hero.getTransactionHash())
					.type(EventType.HERO_CHAIN_UP.getType())
					.status(check)
					.clientId(hero.getClientId())
					.build();
			final String jsonContent = JSON.toJSONString(callBack);
			// 推送响就成功
			String notifyStatus = StringUtils.EMPTY;
			String notifyResult = StringUtils.EMPTY;
			String body = StringUtils.EMPTY;
			try {
				body = post(upChainHeroCallBackUrl, jsonContent);
				if (checkResponse(body)) {
					notifyStatus = "success";
				}
			} catch (Exception e) {
				log.error("upHero err:{}", e.getMessage(), e);
				notifyResult = e.getMessage();
			}
			updateChainHeroCallBack(hero.getId(), callBack, maxBlockNumber.longValue(), notifyStatus, body, notifyResult);
		}
	}
	public void offChain() {
		TaskConfig taskConfig = start(EventType.OFF_CHAIN, Constants.OFF_CHAIN_CALL_BACK);
		if (Objects.isNull(taskConfig)) {
			return;
		}
		List<OffChain> queryOff = queryOff(taskConfig);
		for(OffChain off: queryOff) {
			final BigInteger maxBlockNumber = watchTransferService.getBlockNumber();
			boolean check = check(taskConfig, off.getBlockNumber(), maxBlockNumber, off.getTransactionHash());
			if(!check) {
				continue;
			}
			final OffChainEventCallBack callBack = OffChainEventCallBack.builder()
					.requestId(UUID.randomUUID().toString())
					.transactionHash(off.getTransactionHash())
					.type(EventType.OFF_CHAIN.getType())
					.status(check)
					.clientId(off.getClientId())
					.build();
			final String jsonContent = JSON.toJSONString(callBack);
			// 推送响就成功
			String notifyStatus = StringUtils.EMPTY;
			String notifyResult = StringUtils.EMPTY;
			String body = StringUtils.EMPTY;
			try {
				body = post(offChainCallBackUrl, jsonContent);
				if (checkResponse(body)) {
					notifyStatus = "success";
				}
			} catch (Exception e) {
				log.error("upHero err:{}", e.getMessage(), e);
				notifyResult = e.getMessage();
			}
			updateOffChainCallBack(off.getId(), callBack, maxBlockNumber.longValue(), notifyStatus, body, notifyResult);
		}
	}

	public List<UpChainHero> queryHero(final TaskConfig taskConfig) {
		Page<UpChainHero> page = PageHelper.startPage(1, Constants.CONFIRM_PAGE_SIZE).doSelectPage(() -> {
			upChainHeroMapper.queryNotify(taskConfig.getRetryTimes());
		});
		return page.getResult();
	}

	public List<UpChainFish> queryFish(final TaskConfig taskConfig) {
		Page<UpChainFish> page = PageHelper.startPage(1, Constants.CONFIRM_PAGE_SIZE).doSelectPage(() -> {
			upChainFishMapper.queryNotify(taskConfig.getRetryTimes());
		});
		return page.getResult();
	}

	public List<OffChain> queryOff(final TaskConfig taskConfig) {
		Page<OffChain> page = PageHelper.startPage(1, Constants.CONFIRM_PAGE_SIZE).doSelectPage(() -> {
			offChainMapper.queryNotify(taskConfig.getRetryTimes());
		});
		return page.getResult();
	}

	public int updateChainHeroCallBack(final Integer id, final UpChainEventCallBack callBack, Long notifyBlockNumber, String notityStatus, String response, String notifyResult) {
		final UpChainHero upChainHero = upChainHeroMapper.selectByPrimaryKey(id);
		int nextNotifyNum = Optional.ofNullable(upChainHero.getNotifyNum()).orElse(0) + 1;
		final UpChainHero record = UpChainHero.builder()
				.id(id).notifyTime(new Date())
				.notityStatus(notityStatus)
				.notifyResponse(response)
				.notifyBlockNumber(notifyBlockNumber)
				.notifyParam(JSON.toJSONString(callBack))
				.notifyRid(callBack.getRequestId())
				.notifyResult(notifyResult)
				.notifyNum(nextNotifyNum)  // 每推一次,  就通知的次数就加1
				.build();
		return upChainHeroMapper.updateByPrimaryKeySelective(record);
	}
	public int updateChainFishCallBack(final Long id, final UpChainEventCallBack callBack, Long notifyBlockNumber, String notityStatus, String response, String notifyResult) {
		final UpChainFish upChainFish = upChainFishMapper.selectByPrimaryKey(id);
		int nextNotifyNum = Optional.ofNullable(upChainFish.getNotifyNum()).orElse(0) + 1;
		final UpChainFish record = UpChainFish.builder()
				.id(id).notifyTime(new Date())
				.notityStatus(notityStatus)
				.notifyResponse(response)
				.notifyBlockNumber(notifyBlockNumber)
				.notifyParam(JSON.toJSONString(callBack))
				.notifyRid(callBack.getRequestId())
				.notifyResult(notifyResult)
				.notifyNum(nextNotifyNum)  // 每推一次,  就通知的次数就加1
				.build();
		return upChainFishMapper.updateByPrimaryKeySelective(record);
	}

	public int updateOffChainCallBack(final Long id, final OffChainEventCallBack callBack, Long notifyBlockNumber, String notityStatus, String response, String notifyResult) {
		final OffChain off = offChainMapper.selectByPrimaryKey(id);
		int nextNotifyNum = Optional.ofNullable(off.getNotifyNum()).orElse(0) + 1;
		final OffChain record = OffChain.builder()
				.id(id).notifyTime(new Date())
				.notityStatus(notityStatus)
				.notifyResponse(response)
				.notifyBlockNumber(notifyBlockNumber)
				.notifyParam(JSON.toJSONString(callBack))
				.notifyRid(callBack.getRequestId())
				.notifyResult(notifyResult)
				.notifyNum(nextNotifyNum)  // 每推一次,  就通知的次数就加1
				.build();
		return offChainMapper.updateByPrimaryKeySelective(record);
	}
}
